//===--- ExistentialCollection.swift.gyb ----------------------*- swift -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
// RUN: %target-run-simple-swiftgyb
// REQUIRES: executable_test

// With a non-optimized stdlib the test takes very long.
// REQUIRES: optimized_stdlib

%{

from gyb_stdlib_support import (
    TRAVERSALS,
    collectionForTraversal
)

}%

import StdlibUnittest
import StdlibCollectionUnittest

// Check that the generic parameter is called 'Element'.
protocol TestProtocol1 {}

extension AnyIterator where Element : TestProtocol1 {
  var _elementIsTestProtocol1: Bool {
    fatalError("not implemented")
  }
}

extension AnySequence where Element : TestProtocol1 {
  var _elementIsTestProtocol1: Bool {
    fatalError("not implemented")
  }
}

extension AnyCollection where Element : TestProtocol1 {
  var _elementIsTestProtocol1: Bool {
    fatalError("not implemented")
  }
}

extension AnyBidirectionalCollection where Element : TestProtocol1 {
  var _elementIsTestProtocol1: Bool {
    fatalError("not implemented")
  }
}

extension AnyRandomAccessCollection where Element : TestProtocol1 {
  var _elementIsTestProtocol1: Bool {
    fatalError("not implemented")
  }
}

func storesSameUnderlyingCollection<
  L: _AnyCollectionProtocol, R: _AnyCollectionProtocol
>(_ lhs: L, _ rhs: R) -> Bool {
  return lhs._boxID == rhs._boxID
}

var tests = TestSuite("ExistentialCollection")

tests.test("AnyIterator") {
  func digits() -> AnyIterator<OpaqueValue<Int>> {
    let integers: CountableRange = 0..<5
    let lazyStrings = integers.lazy.map { OpaqueValue($0) }

    // This is a really complicated type of no interest to our
    // clients.
    let iterator: LazyMapSequence<
      CountableRange<Int>, OpaqueValue<Int>
    >.Iterator = lazyStrings.makeIterator()
    return AnyIterator(iterator)
  }
  expectEqual([0, 1, 2, 3, 4], Array(digits()).map { $0.value })

  var x = 7
  let iterator = AnyIterator<Int> {
    if x >= 15 { return nil }
    x += 1
    return x-1
  }
  expectEqual([ 7, 8, 9, 10, 11, 12, 13, 14 ], Array(iterator))
}

tests.test("AnySequence.init(Sequence)") {
  do {
    let base = MinimalSequence<OpaqueValue<Int>>(elements: [])
    var s = AnySequence(base)
    expectType(AnySequence<OpaqueValue<Int>>.self, &s)
    checkSequence([], s, resiliencyChecks: .none) { $0.value == $1.value }
  }
  do {
    let intData = [ 1, 2, 3, 5, 8, 13, 21 ]
    let data = intData.map(OpaqueValue.init)
    let base = MinimalSequence(elements: data)
    var s = AnySequence(base)
    expectType(AnySequence<OpaqueValue<Int>>.self, &s)
    checkSequence(data, s, resiliencyChecks: .none) { $0.value == $1.value }
  }
}

tests.test("AnySequence.init(() -> Generator)") {
  do {
    var s = AnySequence {
      return MinimalIterator<OpaqueValue<Int>>([])
    }
    expectType(AnySequence<OpaqueValue<Int>>.self, &s)
    checkSequence([], s, resiliencyChecks: .none) { $0.value == $1.value }
  }
  do {
    let intData = [ 1, 2, 3, 5, 8, 13, 21 ]
    let data = intData.map(OpaqueValue.init)
    var s = AnySequence {
      return MinimalIterator(data)
    }
    expectType(AnySequence<OpaqueValue<Int>>.self, &s)
    checkSequence(data, s, resiliencyChecks: .none) { $0.value == $1.value }
  }
}

% for Traversal in ['Sequence'] + TRAVERSALS:
%   if Traversal == 'Sequence':
%     TestedType = 'AnySequence'
%     LoggingWrapper = 'LoggingSequence'
%     MinimalBase = 'MinimalSequence'
%   else:
%     TestedType = 'Any' + collectionForTraversal(Traversal)
%     LoggingWrapper = 'Logging' + collectionForTraversal(Traversal)
%     MinimalBase = 'Minimal' + collectionForTraversal(Traversal)
%   end

tests.test("${TestedType}: dispatch to wrapped, SequenceLog") {
  typealias Base = ${LoggingWrapper}<${MinimalBase}<OpaqueValue<Int>>>
  typealias Log = SequenceLog
  let base = Base(wrapping:
    ${MinimalBase}(elements: (0..<10).map(OpaqueValue.init)))
  var s = ${TestedType}(base)
  expectType(${TestedType}<OpaqueValue<Int>>.self, &s)

  Log.makeIterator.expectIncrement(Base.self) {
    _ = s.makeIterator()
  }

  Log.underestimatedCount.expectIncrement(Base.self) {
    _ = s.underestimatedCount
  }

  Log._customContainsEquatableElement.expectIncrement(Base.self) {
    _ = s._customContainsEquatableElement(OpaqueValue(42))
  }

  Log._copyToContiguousArray.expectIncrement(Base.self) {
    _ = s._copyToContiguousArray()
  }

  do {
    var result = Array(repeating: OpaqueValue(0), count: 10)
    Log._copyContents.expectIncrement(Base.self) {
      result.withUnsafeMutableBufferPointer {
        _ = s._copyContents(initializing: $0)
      }
    }
  }
}

%   if Traversal != 'Sequence':
tests.test("${TestedType}: dispatch to wrapped, CollectionLog") {
  typealias Base = ${LoggingWrapper}<${MinimalBase}<OpaqueValue<Int>>>
  typealias Log = CollectionLog
  let base = Base(wrapping:
    ${MinimalBase}(elements: (0..<10).map(OpaqueValue.init)))
  var c = ${TestedType}(base)
  expectType(${TestedType}<OpaqueValue<Int>>.self, &c)

  do {
    var i = c.startIndex
    // `startIndex` is cached in the type erased wrapper, so first mutation
    // makes a unique copy.
    Log.successor.expectIncrement(Base.self) {
      Log.formSuccessor.expectUnchanged(Base.self) {
        c.formIndex(after: &i)
      }
    }
    Log.successor.expectUnchanged(Base.self) {
      Log.formSuccessor.expectIncrement(Base.self) {
        c.formIndex(after: &i)
      }
    }
  }

%   if Traversal in ['Bidirectional', 'RandomAccess']:
  do {
    typealias Log = BidirectionalCollectionLog
    var i = c.endIndex
    // `startIndex` is cached in the type erased wrapper, so first mutation
    // makes a unique copy.
    Log.predecessor.expectIncrement(Base.self) {
      Log.formPredecessor.expectUnchanged(Base.self) {
        c.formIndex(before: &i)
      }
    }
    Log.predecessor.expectUnchanged(Base.self) {
      Log.formPredecessor.expectIncrement(Base.self) {
        c.formIndex(before: &i)
      }
    }
  }
%   end

  do {
    let startIndex = c.startIndex
    var badBounds = c.index(c.startIndex, offsetBy: 3)..<c.endIndex

    Log.subscriptIndex.expectIncrement(Base.self) {
      Log.subscriptRange.expectUnchanged(Base.self) {
        _ = c[startIndex]
      }
    }

    Log.subscriptIndex.expectUnchanged(Base.self) {
      Log.subscriptRange.expectIncrement(Base.self) {
        _ = c[startIndex..<startIndex]
      }
    }

    Log._failEarlyRangeCheckIndex.expectUnchanged(Base.self) {
      Log._failEarlyRangeCheckRange.expectUnchanged(Base.self) {
        // Early range checks are not forwarded for performance reasons.
        _ = c._failEarlyRangeCheck(startIndex, bounds: badBounds)
        _ = c._failEarlyRangeCheck(startIndex..<startIndex, bounds: badBounds)
      }
    }
  }

  do {
    var i = c.startIndex
    Log.successor.expectIncrement(Base.self) {
      Log.formSuccessor.expectUnchanged(Base.self) {
        i = c.index(after: i)
      }
    }

    Log.successor.expectUnchanged(Base.self) {
      Log.formSuccessor.expectIncrement(Base.self) {
        c.formIndex(after: &i)
      }
    }

    var x = i
    Log.successor.expectIncrement(Base.self) {
      Log.formSuccessor.expectUnchanged(Base.self) {
        i = c.index(after: i)
      }
    }
    _blackHole(x)
  }

%   if Traversal in ['Bidirectional', 'RandomAccess']:
  do {
    typealias Log = BidirectionalCollectionLog
    var i = c.endIndex

    Log.predecessor.expectIncrement(Base.self) {
      Log.formPredecessor.expectUnchanged(Base.self) {
        i = c.index(before: i)
      }
    }

    Log.predecessor.expectUnchanged(Base.self) {
      Log.formPredecessor.expectIncrement(Base.self) {
        c.formIndex(before: &i)
      }
    }

    var x = i
    Log.predecessor.expectIncrement(Base.self) {
      Log.formPredecessor.expectUnchanged(Base.self) {
        i = c.index(before: i)
      }
    }
    _blackHole(x)
  }
%   end
}
%   end
% end

tests.test("ForwardCollection") {
  let a0: ContiguousArray = [1, 2, 3, 5, 8, 13, 21]
  let fc0 = AnyCollection(a0)
  let a1 = ContiguousArray(fc0)
  expectEqual(a0, a1)
  for e in a0 {
    let i = fc0.firstIndex(of: e)
    expectNotNil(i)
    expectEqual(e, fc0[i!])
  }
  for i in fc0.indices {
    expectNotEqual(fc0.endIndex, i)
    expectEqual(1, fc0.indices.filter { $0 == i }.count)
  }
}

tests.test("BidirectionalCollection") {
  let a0: ContiguousArray = [1, 2, 3, 5, 8, 13, 21]
  let fc0 = AnyCollection(a0.lazy.reversed())

  let bc0_ = AnyBidirectionalCollection(fc0)  // upgrade!
  expectNotNil(bc0_)
  let bc0 = bc0_!
  expectTrue(storesSameUnderlyingCollection(fc0, bc0))

  let fc1 = AnyCollection(a0.lazy.reversed()) // new collection
  expectFalse(storesSameUnderlyingCollection(fc1, fc0))

  let fc2 = AnyCollection(bc0)                // downgrade
  expectTrue(storesSameUnderlyingCollection(fc2, bc0))

  let a1 = ContiguousArray(bc0.lazy.reversed())
  expectEqual(a0, a1)
  for e in a0 {
    let i = bc0.firstIndex(of: e)
    expectNotNil(i)
    expectEqual(e, bc0[i!])
  }
  for i in bc0.indices {
    expectNotEqual(bc0.endIndex, i)
    expectEqual(1, bc0.indices.filter { $0 == i }.count)
  }

  // Can't upgrade a non-random-access collection to random access
  let s0 = "Hello, Woyld"
  let bc1 = AnyBidirectionalCollection(s0)
  let fc3 = AnyCollection(bc1)
  expectTrue(storesSameUnderlyingCollection(fc3, bc1))
  expectNil(AnyRandomAccessCollection(bc1))
  expectNil(AnyRandomAccessCollection(fc3))
}

tests.test("RandomAccessCollection") {
  let a0: ContiguousArray = [1, 2, 3, 5, 8, 13, 21]
  let fc0 = AnyCollection(a0.lazy.reversed())
  let rc0_ = AnyRandomAccessCollection(fc0)         // upgrade!
  expectNotNil(rc0_)
  let rc0 = rc0_!
  expectTrue(storesSameUnderlyingCollection(rc0, fc0))

  let bc1 = AnyBidirectionalCollection(rc0)         // downgrade
  expectTrue(storesSameUnderlyingCollection(bc1, rc0))

  let fc1 = AnyBidirectionalCollection(rc0)         // downgrade
  expectTrue(storesSameUnderlyingCollection(fc1, rc0))

  let a1 = ContiguousArray(rc0.lazy.reversed())
  expectEqual(a0, a1)
  for e in a0 {
    let i = rc0.firstIndex(of: e)
    expectNotNil(i)
    expectEqual(e, rc0[i!])
  }
  for i in rc0.indices {
    expectNotEqual(rc0.endIndex, i)
    expectEqual(1, rc0.indices.filter { $0 == i }.count)
  }
}

% for Traversal in TRAVERSALS:
%   AnyCollection = 'Any' + collectionForTraversal(Traversal)
%   Base = 'Minimal' + collectionForTraversal(Traversal)

do {
  var AnyCollectionTests = TestSuite("${AnyCollection}")

  func makeCollection(elements: [OpaqueValue<Int>])
    -> ${AnyCollection}<OpaqueValue<Int>> {
    let base = ${Base}(elements: elements)
    return ${AnyCollection}(base)
  }

  func makeCollectionOfEquatable(elements: [MinimalEquatableValue])
    -> ${AnyCollection}<MinimalEquatableValue> {
    let base = ${Base}(elements: elements)
    return ${AnyCollection}(base)
  }

  func makeCollectionOfComparable(elements: [MinimalComparableValue])
    -> ${AnyCollection}<MinimalComparableValue> {
    let base = ${Base}(elements: elements)
    return ${AnyCollection}(base)
  }


  AnyCollectionTests.add${collectionForTraversal(Traversal)}Tests(
    "tests.",
    makeCollection: makeCollection,
    wrapValue: identity,
    extractValue: identity,
    makeCollectionOfEquatable: makeCollectionOfEquatable,
    wrapValueIntoEquatable: identityEq,
    extractValueFromEquatable: identityEq,
    resiliencyChecks: CollectionMisuseResiliencyChecks.all,
    collectionIsBidirectional: ${'false' if Traversal == 'Forward' else 'true'}
  )
}

% end

runAllTests()
